﻿// -----------------------------------------------------------------------
// <copyright file="BubbleSession.cs" company="">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------

namespace BubbleBlastSolver.Classes.BusinessLogic
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Diagnostics;

    /// <summary>
    /// TODO: Update summary.
    /// </summary>
    public class BubbleSession
    {
        public const uint ROW_COUNT = 6; 
        public const uint COLUMN_COUNT = 5;
        public const uint LAST_ROW_INDEX = ROW_COUNT - 1;
        public const uint LAST_COL_INDEX = COLUMN_COUNT - 1;
        
        private Bubble[,] m_bubblesArray = new Bubble[ROW_COUNT, COLUMN_COUNT];
        private List<Bullet> m_bulletsCollection = new List<Bullet>();
        private uint m_currentClickRow = 0;
        private uint m_currentClickCol = 0;
        private bool m_IsEmpty = false;

        private uint m_index = 1;

        /// <summary>
        /// Constructore
        /// </summary>
        public BubbleSession()
        {
        }

        public void Initialize()
        {
            if (this.IsEmpty)
            {
                return;
            }

            this.SetFirstClickPosition();
        }

        public uint BubblesCount
        {
            get
            {
                uint count = 0;
                for( uint row = 0 ; row < ROW_COUNT ; ++row)
                {
                    for (uint column = 0; column < COLUMN_COUNT; ++column)
                    {
                        if( this.GetBubble(row, column) != null ) 
                        {
                            ++count;                        
                        }
                    }
                }

                return count;
            }
        }

        public uint Index
        {
            get
            {
                return this.m_index;
            }
            set
            {
                this.m_index = value;
            }
        }

        public bool MoveToNextClickPosition()
        {
            // start iterating the bubble grid from the current position to the last bottom right bubble.
            for (uint row = 0; row < ROW_COUNT; ++row)
            {
                for (uint col = 0; col < COLUMN_COUNT; ++col)
                {
                    if (row < this.m_currentClickRow)
                    {
                        continue;
                    }
                    else if (row == this.m_currentClickRow)
                    {
                        if (col <= this.m_currentClickCol)
                        {
                            continue;
                        }
                    }

                    if (this.GetBubble(row, col) == null)
                    {
                        continue;
                    }
                    else
                    {
                        this.m_currentClickRow = row;
                        this.m_currentClickCol = col;
                        return true;
                    }
                }
            }

            return false;
        }
        
        public void CreateBubbleIn(uint row, uint column, uint weight) 
        {
            this.SetBubble(row, column, new Bubble(weight));
        }

        public BubbleSession CloneMe()
        {
            BubbleSession nextBubbleSession = new BubbleSession();

            for (uint rowIndex = 0; rowIndex < ROW_COUNT; ++rowIndex)
            {
                for (uint columnIndex = 0; columnIndex < COLUMN_COUNT; ++columnIndex)
                {
                    if (this.GetBubble(rowIndex, columnIndex) != null)
                    {
                        nextBubbleSession.CreateBubbleIn(rowIndex, columnIndex, this.GetBubble(rowIndex, columnIndex).Weight);
                    }
                }
            }

            nextBubbleSession.Index = this.m_index + 1;
            return nextBubbleSession;
        }

        public BubbleSession ClickAtCurrentPosition()
        {
            return this.ClickAtPosition(this.m_currentClickRow, this.m_currentClickCol);
        }

        public BubbleSession ClickAtPosition(uint row, uint column)
        {
            //Debug.WriteLine(String.Format("Session {0} ({1},{2})", this.m_index.ToString(), this.m_currentClickRow, this.m_currentClickCol));

            BubbleSession nextSession = this.CloneMe();
            Bubble bubble = nextSession.GetBubble(row, column);

            if (bubble == null)
            {
                throw new Exception(String.Format("No bubble exists in {0},{1}", row.ToString(), column.ToString()));
            }

            if (bubble.WillBlast)
            {
                nextSession.BlastAt(row, column);          // blast the bubble
                nextSession.ConsumeBulletsRecursively();   // Consume bullets
            }
            else
            {
                bubble.Eat();   // This decrements the count without blast
                Debug.Assert(bubble.Weight > 0);
            }

            nextSession.Initialize();

            return nextSession;
        }

        public bool IsEmpty
        {
            get
            {
                for (uint row = 0; row < ROW_COUNT; ++row)
                {
                    for (uint col = 0; col < COLUMN_COUNT; ++col)
                    {
                        if (this.GetBubble(row, col) != null)
                        {
                            return false;
                        }
                    }
                }

                return true;
            }
        }

        public string DisplayName
        {
            get
            {
                return String.Format("Session {0}", this.m_index.ToString());
            }
        }

        public string DisplayPositionName
        {
            get
            {
                return String.Format("{0}, {1}", this.m_currentClickRow.ToString(), this.m_currentClickCol.ToString());
            }
        }

        private void SetFirstClickPosition()
        {
            // set current row and col position
            for (uint row = 0; row < ROW_COUNT; ++row)
            {
                for (uint col = 0; col < COLUMN_COUNT; ++col)
                {
                    if (this.GetBubble(row, col) != null)
                    {
                        this.m_currentClickRow = row;
                        this.m_currentClickCol = col;
                        return;
                    }
                }
            }

            Debug.Assert(false, "SetFirstClickPosition failed to set the first click position!");
        }

        private void SetBubble(uint row, uint column, Bubble bubble)
        {
            this.m_bubblesArray[row, column] = bubble;
        }

        private Bubble GetBubble(uint row, uint column)
        {
            return this.m_bubblesArray[row, column];
        }

        private void BlastAt(uint row, uint column)
        {
            // Remove the bubble from row, col
            this.SetBubble(row, column, null);

            this.AddBulletsIntoCollection(row, column, this.m_bulletsCollection);
        }

        private void BlastAt(uint row, uint column, List<Bullet> blastBullets)
        {
            this.SetBubble(row, column, null);

            this.AddBulletsIntoCollection(row, column, blastBullets);
        }

        private void AddBulletsIntoCollection(uint row, uint column, List<Bullet> bullets)
        {
            bullets.Add(new Bullet(row, column, Bullet.BulletDirection.Left));
            bullets.Add(new Bullet(row, column, Bullet.BulletDirection.Down));
            bullets.Add(new Bullet(row, column, Bullet.BulletDirection.Right));
            bullets.Add(new Bullet(row, column, Bullet.BulletDirection.Up));

        }

        private bool IsPositionEmpty(uint row, uint column)
        {
            return this.GetBubble(row, column) == null;
        }

        private void ConsumeBulletsRecursively()
        {
            if (this.m_bulletsCollection.Count == 0)
            {
                return; // This ends the recursive function call.
            }

            Bullet bullet = null;
            int bulletIndex = 0;
            List<Bullet> newBullets = new List<Bullet>();
            List<Bullet> deletedBullets = new List<Bullet>();

            for(bulletIndex = 0 ; bulletIndex < this.m_bulletsCollection.Count ; ++ bulletIndex)
            {
                bullet = this.m_bulletsCollection[bulletIndex];

                // Four possibilites for the bullet
                // 1. It gets out of the game border!
                // 2. It moves to next empty bubble poition
                // 3. It blasts a bubble
                // 4. It gets eaten by a bubble

                if (bullet.IsNextPositionOutOfGame())
                {
                    // POSSIBILITY 1
                    //this.m_bulletsCollection.RemoveAt(bulletIndex);                    
                    deletedBullets.Add(bullet);
                    continue;                        
                }

                // Get possible next position
                uint nextRow;
                uint nextColumn;
                bullet.GetNextPosition(out nextRow, out nextColumn);

                // What will happen in the next position
                if(this.IsPositionEmpty(nextRow, nextColumn))
                {
                    // POSSIBILITY 2
                    bullet.Move();
                    //if (bulletIndex + 1 < this.m_bulletsCollection.Count) ++bulletIndex;
                    continue;                
                }

                // we have a bubble in the next position
                Bubble bubbleAtNextPosition = this.GetBubble(nextRow, nextColumn);
                if (bubbleAtNextPosition.WillBlast)
                {
                    // POSSIBILITY 3
                    //this.m_bulletsCollection.RemoveAt(bulletIndex);
                    deletedBullets.Add(bullet);
                    this.BlastAt(nextRow, nextColumn, newBullets);
                    //++bulletIndex; // no need to check for index bound because blast adds 4 new bullets in the collection.
                    continue;
                }
                else
                {
                    // POSSIBILITY 4
                    bubbleAtNextPosition.Eat();
                    //this.m_bulletsCollection.RemoveAt(bulletIndex);                    
                    deletedBullets.Add(bullet);
                    continue;
                }                
            }

            foreach (Bullet deletedBullet in deletedBullets)
            {
                this.m_bulletsCollection.Remove(deletedBullet);
            }
            deletedBullets.Clear();

            foreach (Bullet newBullet in newBullets)
            {
                //this.m_bulletsCollection.Add(newBullet);
                this.m_bulletsCollection.Insert(0, newBullet);
            }
            newBullets.Clear();

            // Call recursively untill all bullets collection becomes empty
            this.ConsumeBulletsRecursively();
        }

        /// <summary>
        /// Private bullet class
        /// </summary>
        private class Bullet
        {
            public enum BulletDirection
            {
                Up = 0,
                Down,
                Right,
                Left
            };

            private uint m_row = 0;
            private uint m_column = 0;
            private BulletDirection m_bulletDirection;

            public Bullet(uint row, uint column, BulletDirection direction)
            {
                this.m_row = row;
                this.m_column = column;
                this.m_bulletDirection = direction;
            }

            public bool IsNextPositionOutOfGame()
            {
                switch (this.m_bulletDirection)
                {
                    case BulletDirection.Up:

                        if (this.m_row == 0)
                        {
                            return true;
                        }

                        break;

                    case BulletDirection.Down:

                        if (this.m_row == ROW_COUNT - 1)
                        {
                            return true;
                        }

                        break;

                    case BulletDirection.Left:

                        if (this.m_column == 0)
                        {
                            return true;
                        }

                        break;

                    case BulletDirection.Right:

                        if (this.m_column == COLUMN_COUNT - 1)
                        {
                            return true;
                        }

                        break;
                }

                return false;
            }

            public void GetNextPosition(out uint row, out uint column)
            {
                row = 0;
                column = 0;

                switch (this.m_bulletDirection)
                {
                    case BulletDirection.Up:

                        if (this.m_row > 0)
                        {
                            row = this.m_row - 1;
                            column = this.m_column;
                        }
                        else
                        {
                            Debug.Assert(false, "Bullet beyond row 0 in GetNextPosition!");
                        }

                        break;

                    case BulletDirection.Down:

                        if (this.m_row < ROW_COUNT - 1)
                        {
                            row = this.m_row + 1;
                            column = this.m_column;
                        }
                        else
                        {
                            Debug.Assert(false, "Bullet beyond last row in GetNextPosition!");
                        }

                        break;

                    case BulletDirection.Left:

                        if (this.m_column > 0)
                        {
                            row = this.m_row;
                            column = this.m_column - 1;
                        }
                        else
                        {
                            Debug.Assert(false, "Bullet beyond col 0 in GetNextPosition!");
                        }

                        break;

                    case BulletDirection.Right:

                        if (this.m_column < COLUMN_COUNT - 1)
                        {
                            row = this.m_row;
                            column = this.m_column + 1;
                        }
                        else
                        {
                            Debug.Assert(false, "Bullet beyond last col in GetNextPosition!");
                        }

                        break;                       
                }
            }

            public void Move()
            {
                switch (this.m_bulletDirection)
                {
                    case BulletDirection.Up:

                        --this.m_row;

                        break;

                    case BulletDirection.Down:

                        ++this.m_row;

                        break;

                    case BulletDirection.Left:

                        --this.m_column;
                        break;

                    case BulletDirection.Right:

                        ++this.m_column;
                        break;
                }
            }

        }
    }
}
